# EnlargeWeb source code
# Licenced under GPLv3
# Stanislav Yudin

import os, sys
from optparse import OptionParser
from xml.dom import minidom
import logging
import datetime
import subprocess
from paste.deploy import appconfig
from enlargeweb.config.environment import load_environment
from enlargeweb.model.srv import Server, ServerAppliance
from enlargeweb.model.act import Activity
from enlargeweb.model.srv_act import ServerActivity
from enlargeweb.model import meta
from pylons import config

__all__ = ['configFromFile', 'configToXml', 'Worker', 'WorkerError']

def spawn(plugin_id, description, arguments):
	logging.debug('Spawning plugin id:%s description:%s arguments: %s' % (plugin_id, description, arguments))
	instance = getPluginInstance(plugin_id)
	if not instance:
		logging.fatal('Failed to create plugin instance with id %s' % plugin_id)
		return None
	new_act = Activity(None,
				description,
				datetime.datetime.now(),
				0,
				arguments['owner'])

	meta.Session.add(new_act)
	meta.Session.flush()
	
	#get it back
	act = meta.Session.query(Activity).filter(Activity.id == new_act.id).with_lockmode('update').one()
	arguments['activity_id'] = act.id
	arguments['description'] = description
	
	cfg_path = os.path.join(config['workers.cfgpath'], 'wcfg_%d.xml' % act.id)

	configToFile(arguments, cfg_path)

	act.put_log('Starting new activity')

	worker = subprocess.Popen(
		args = [config['workers.python'],
				config['workers.process'],
				'-c', cfg_path,
				'-l', config['workers.logpath']],
		stdout = None,
		stdin = None,
		close_fds = True,
	)
	act.servers.add_new( int(arguments['host_id']), True)
	act.proc_id = worker.pid
	meta.Session.commit()

	return act

class WorkerError(Exception):
	def __init__(self, msg):
		self.msg = msg

	def __str__(self):
		return self.msg

def _parseCfgDom(dom):
	"""
	Parse XML configuration
	"""
	cfg_root = dom.documentElement

	if not cfg_root:
		self.log.error('Malformed configuration')
		raise WorkerError("Malformed configuration")

	config = {}
	for node in cfg_root.childNodes:
		if node.nodeType == minidom.Node.ELEMENT_NODE:
			if node.firstChild is not None:  # check if value is present
				config[node.nodeName] = node.firstChild.nodeValue
			else:
				config[node.nodeName] = None

	return config

def configFromStr(cfg_str):
	"""
	Load XML config from string
	"""
	dom = minidom.parseString(cfg_str)
	return _parseCfgDom(dom)

def configFromFile(cfg_path):
	"""
	Load XML config from file
	"""
	dom = minidom.parse(cfg_path)
	return _parseCfgDom(dom)

def configToFile(param_dict, filepath):
	"""
	Save XML config to file
	"""
	dom = minidom.getDOMImplementation().createDocument(
		None, 'config', None)
	print param_dict
	for k,v in param_dict.iteritems():
		key_el = dom.createElement(k)
		val_el = dom.createTextNode(str(v))
		key_el.appendChild(val_el)
		dom.documentElement.appendChild(key_el)

	f = open(filepath, 'w')
	dom.writexml(f)
	f.close()
	
def getPluginInstance(type_id, dirpath = 'enlargeweb/plugins'):
	eweb_dir = os.path.dirname(os.getenv('enlargeweb_config'))
	dirpath = os.path.join(eweb_dir, dirpath)
	sys.path.insert( 1, dirpath )
	print sys.path

	files = os.listdir(dirpath)
	files = filter(
		lambda x : x.endswith('.py'),
		files)

	plugins_meta = []
	for file in files:
		module = __import__(file[:-3], globals(), locals(), [], -1)

		for c in dir(module):
			if c.startswith('Plugin'):
				plugin_info = getattr(module, c).info
				if plugin_info.type_id == type_id:
					class_obj = getattr(module, c)
					instance = class_obj.__new__(class_obj)
					return instance

	log.error('failed to create plugin with id %s' % type_id)
	return None

class Worker(object):
	def __init__(self):
		parser = OptionParser()

		parser.add_option(
			'-c', '--config-xml', dest = 'cfg_path', help = 'configuration XML file path', default = None
		)
		parser.add_option(
			'-l', '--log-path', dest = 'log_path', help = 'worker log file path', default = './'
		)

		(self.options, args) = parser.parse_args()
		self.config = {}
		if self.options.cfg_path is None:
			self.config = configFromStr(sys.stdin.read())
		else:
			self.config = configFromFile(self.options.cfg_path)

		#make sure there is logs folder
		if not os.path.exists(self.options.log_path):
			os.mkdir(self.options.log_path)
		#create log for worker
		logpath = os.path.join(
			self.options.log_path,
			"%s-%s.log" % (self.config['activity_id'],
						   str(self.config['appl_name']).replace(' ', '_')),
		)
		#get activity instance
		self.activity = meta.Session.query(Activity).filter(Activity.id == int(self.config['activity_id'])).with_lockmode('update').one()
		
		handler = logging.FileHandler(logpath, 'w')
		handler.setFormatter(
			logging.Formatter('%(asctime)s [%(levelname)s] - %(message)s')
		)
		self.log = logging.getLogger('worker_%s' % self.config['activity_id'])
		self.log.addHandler(handler)
		self.log.setLevel(logging.DEBUG)

	def save_appliance(self):
		self.log.info('saving appliance:%s on host:%s' % ( self.config['appl_id'], self.config['host_id']))
		sa = ServerAppliance()
		sa.node_id = int(self.config['host_id'])
		sa.activity_id = int(self.config['activity_id'])
		sa.appliance_id = int(self.config['appl_id'])
		meta.Session.add(sa)
		meta.Session.commit()
		
	def remove_appliances(self):
		sa_list = meta.Session.query(ServerAppliance).filter(ServerAppliance.node_id == int(self.config['host_id']))
		for sa in sa_list:
			self.log.info('removing server appliance: %s' % str(sa))
			meta.Session.delete(sa_list)

	def shutdown(self, ret_code):
		self.log.info("Worker is shutting down... Retcode = %d" % ret_code)
		sys.exit(ret_code)

	def start(self):
		self.log.info("Worker is starting...")
		self.log.info("Activity: %s, type: %s" % \
					   (self.config['activity_id'], self.config['appl_plugin_id']))
		self.log.info("Using configuration data: %s" % repr(self.config))
		self.log.info('creating plugin')
		plugin = getPluginInstance(self.config['appl_plugin_id'])
		#self.call_operate(plugin)
		try:
			self.call_operate(plugin)
		except Exception, ex:
			self.log.info('plugin failed with %s', str(ex))
			self.activity.error(self.config['owner'], str(ex), -1)
			self.shutdown(-1)
			
	def call_operate(self, plugin):
		self.log.info('calling plugin->operate')
		plugin.operate(self.config, self.log)
		self.log.info('plugin finished ok')
		#we except plugin to set activity status to something
		meta.Session.refresh(self.activity)
		self.log.info('activity status after plugin:%s' % self.activity.status)
		if self.activity.status == 2:
			self.save_appliance()
		self.shutdown(0)

if __name__ == '__main__':
	# read path to cfg from parent's environment
	# setup Pylons environment
	os.chdir(os.path.dirname(os.getenv('enlargeweb_config')))
	conf = appconfig('config:' + os.getenv('enlargeweb_config'))
	load_environment(conf.global_conf, conf.local_conf)
	w = Worker()
	w.start()
